<h2>Why is this an issue?</h2>
<p>Both the <code>List.Find</code> method and the <code>Enumerable.FirstOrDefault</code> method can be used to locate the first element that meets a
specified condition within a collection. However, for <code>List</code> objects, <code>List.Find</code> may offer superior performance compared to
<code>Enumerable.FirstOrDefault</code>. While the performance difference might be negligible for small collections, it can become significant for
larger collections. This observation also holds true for <code>ImmutableList</code> and arrays.</p>
<p>It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the <a
href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/#collections">performance improvements in .NET 9</a> have brought
<code>FirstOrDefault</code> closer to the performance of collection-specific <code>Find</code> methods in most scenarios.</p>
<p><strong>Applies to</strong></p>
<ul>
  <li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.find">List</a> </li>
  <li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.array.find">Array</a> </li>
  <li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable.immutablelist-1.find">ImmutableList</a> </li>
</ul>
<h3>What is the potential impact?</h3>
<p>We measured at least 2x improvement in the execution time. For more details see the <code>Benchmarks</code> section from the <code>More info</code>
tab.</p>
<h2>How to fix it</h2>
<p>The <code>Find</code> method is defined on the collection class, and it has the same signature as <code>FirstOrDefault</code> extension method. The
function can be replaced in place.</p>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
int GetValue(List&lt;int&gt; data) =&gt;
    data.FirstOrDefault(x =&gt; x % 2 == 0);
</pre>
<pre data-diff-id="2" data-diff-type="noncompliant">
int GetValue(int[] data) =&gt;
    data.FirstOrDefault(x =&gt; x % 2 == 0);
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
int GetValue(List&lt;int&gt; data) =&gt;
    data.Find(x =&gt; x % 2 == 0);
</pre>
<pre data-diff-id="2" data-diff-type="compliant">
int GetValue(int[] data) =&gt;
    Array.Find(data, x =&gt; x % 2 == 0);
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
  <li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.find">List&lt;T&gt;.Find(Predicate&lt;T&gt;)</a> </li>
  <li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.array.find">Array.Find&lt;T&gt;(T[], Predicate&lt;T&gt;)</a> </li>
  <li> <a
  href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable.immutablelist-1.find">ImmutableList&lt;T&gt;.Find(Predicate&lt;T&gt;)</a> </li>
  <li> <a href="https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.firstordefault">Enumerable.FirstOrDefault(Predicate&lt;T&gt;)</a>
  </li>
</ul>
<h3>Benchmarks</h3>
<table>
  <colgroup>
    <col style="width: 16.6666%;">
    <col style="width: 16.6666%;">
    <col style="width: 16.6666%;">
    <col style="width: 16.6666%;">
    <col style="width: 16.6666%;">
    <col style="width: 16.667%;">
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Runtime</th>
      <th>Categories</th>
      <th>Mean</th>
      <th>Standard Deviation</th>
      <th>Allocated</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><p>ArrayFirstOrDefault</p></td>
      <td><p>.NET 8.0</p></td>
      <td><p>Array</p></td>
      <td><p>10.515 μs</p></td>
      <td><p>0.1410 μs</p></td>
      <td><p>32 B</p></td>
    </tr>
    <tr>
      <td><p>ArrayFind</p></td>
      <td><p>.NET 8.0</p></td>
      <td><p>Array</p></td>
      <td><p>4.417 μs</p></td>
      <td><p>0.0729 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ArrayFirstOrDefault</p></td>
      <td><p>.NET 9.0</p></td>
      <td><p>Array</p></td>
      <td><p>2.262 μs</p></td>
      <td><p>0.0135 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td><p>ArrayFind</p></td>
      <td><p>.NET 9.0</p></td>
      <td><p>Array</p></td>
      <td><p>3.428 μs</p></td>
      <td><p>0.0206 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ArrayFirstOrDefault</p></td>
      <td><p>.NET Framework 4.8.1</p></td>
      <td><p>Array</p></td>
      <td><p>45.074 μs</p></td>
      <td><p>0.7517 μs</p></td>
      <td><p>32 B</p></td>
    </tr>
    <tr>
      <td><p>ArrayFind</p></td>
      <td><p>.NET Framework 4.8.1</p></td>
      <td><p>Array</p></td>
      <td><p>13.948 μs</p></td>
      <td><p>0.1496 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ImmutableListFirstOrDefault</p></td>
      <td><p>.NET 8.0</p></td>
      <td><p>ImmutableList&lt;T&gt;</p></td>
      <td><p>83.796 μs</p></td>
      <td><p>1.3199 μs</p></td>
      <td><p>72 B</p></td>
    </tr>
    <tr>
      <td><p>ImmutableListFind</p></td>
      <td><p>.NET 8.0</p></td>
      <td><p>ImmutableList&lt;T&gt;</p></td>
      <td><p>59.720 μs</p></td>
      <td><p>1.0723 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ImmutableListFirstOrDefault</p></td>
      <td><p>.NET 9.0</p></td>
      <td><p>ImmutableList&lt;T&gt;</p></td>
      <td><p>81.984 μs</p></td>
      <td><p>1.0886 μs</p></td>
      <td><p>72 B</p></td>
    </tr>
    <tr>
      <td><p>ImmutableListFind</p></td>
      <td><p>.NET 9.0</p></td>
      <td><p>ImmutableList&lt;T&gt;</p></td>
      <td><p>58.288 μs</p></td>
      <td><p>0.8079 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ImmutableListFirstOrDefault</p></td>
      <td><p>.NET Framework 4.8.1</p></td>
      <td><p>ImmutableList&lt;T&gt;</p></td>
      <td><p>446.893 μs</p></td>
      <td><p>9.8430 μs</p></td>
      <td><p>76 B</p></td>
    </tr>
    <tr>
      <td><p>ImmutableListFind</p></td>
      <td><p>.NET Framework 4.8.1</p></td>
      <td><p>ImmutableList&lt;T&gt;</p></td>
      <td><p>427.476 μs</p></td>
      <td><p>3.3371 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ListFirstOrDefault</p></td>
      <td><p>.NET 8.0</p></td>
      <td><p>List&lt;T&gt;</p></td>
      <td><p>14.808 μs</p></td>
      <td><p>0.1723 μs</p></td>
      <td><p>40 B</p></td>
    </tr>
    <tr>
      <td><p>ListFind</p></td>
      <td><p>.NET 8.0</p></td>
      <td><p>List&lt;T&gt;</p></td>
      <td><p>6.040 μs</p></td>
      <td><p>0.1104 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ListFirstOrDefault</p></td>
      <td><p>.NET 9.0</p></td>
      <td><p>List&lt;T&gt;</p></td>
      <td><p>2.233 μs</p></td>
      <td><p>0.0154 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td><p>ListFind</p></td>
      <td><p>.NET 9.0</p></td>
      <td><p>List&lt;T&gt;</p></td>
      <td><p>4.458 μs</p></td>
      <td><p>0.0745 μs</p></td>
      <td><p>-</p></td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <tr>
      <td><p>ListFirstOrDefault</p></td>
      <td><p>.NET Framework 4.8.1</p></td>
      <td><p>List&lt;T&gt;</p></td>
      <td><p>57.290 μs</p></td>
      <td><p>1.0494 μs</p></td>
      <td><p>40 B</p></td>
    </tr>
    <tr>
      <td><p>ListFind</p></td>
      <td><p>.NET Framework 4.8.1</p></td>
      <td><p>List&lt;T&gt;</p></td>
      <td><p>18.476 μs</p></td>
      <td><p>0.0504 μs</p></td>
      <td><p>-</p></td>
    </tr>
  </tbody>
</table>
<h4>Glossary</h4>
<ul>
  <li> <a href="https://en.wikipedia.org/wiki/Arithmetic_mean">Mean</a> </li>
  <li> <a href="https://en.wikipedia.org/wiki/Standard_deviation">Standard Deviation</a> </li>
  <li> <a href="https://en.wikipedia.org/wiki/Memory_management">Allocated</a> </li>
</ul>
<p>The results were generated by running the following snippet with <a href="https://github.com/dotnet/BenchmarkDotNet">BenchmarkDotNet</a>:</p>
<pre>
// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func&lt;int, bool&gt; ConditionFunc = static x =&gt; x == 1;
private readonly static Predicate&lt;int&gt; ConditionPredicate = static x =&gt; x == 1;
private List&lt;int&gt; list;
private ImmutableList&lt;int&gt; immutableList;
private int[] array;
public const int N = 10_000;

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x =&gt; N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List&lt;T&gt;"), Benchmark(Baseline = true)]
public int ListFirstOrDefault() =&gt;
    list.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("List&lt;T&gt;"), Benchmark]
public int ListFind() =&gt;
    list.Find(ConditionPredicate);

[BenchmarkCategory("ImmutableList&lt;T&gt;"), Benchmark(Baseline = true)]
public int ImmutableListFirstOrDefault() =&gt;
    immutableList.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("ImmutableList&lt;T&gt;"), Benchmark]
public int ImmutableListFind() =&gt;
    immutableList.Find(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public int ArrayFirstOrDefault() =&gt;
    array.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public int ArrayFind() =&gt;
    Array.Find(array, ConditionPredicate);
</pre>
<p>Hardware configuration:</p>
<pre>
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
</pre>

